﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Camera;
using Model;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using Utils;

namespace GameScreen
{
    public class Game : GameWindow
    {
        private World _localWorld;
        private bool _mouseCapture;
        private double _zoom = .08;
        /// <summary>Creates a 800x600 window with the specified title.</summary>
        public Game()
            : base(640, 480, GraphicsMode.Default, "Project 5")
        {
            VSync = VSyncMode.On;
            _localWorld = new World();
            SetupWorld();
        }

        public void LoadWorld(World world)
        {
            _localWorld = world;
        }

        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
            Mouse.Move += MouseMove;
        }

        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            GL.Viewport(0, 0, Width, Height);

            double aspectRatio = Width / (double)Height;

            Matrix4 perspective = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)aspectRatio, 1, 64);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref perspective);

        }

        /// <summary>
        /// Called when it is time to setup the next frame. Add you game logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);

            if (Keyboard[Key.Up])
                _localWorld.TranslateLocal(new Point3D(0, 0, -.3));
            if (Keyboard[Key.Down])
                _localWorld.TranslateLocal(new Point3D(0, 0, .3));
            if (Keyboard[Key.Left])
                _localWorld.TranslateLocal(new Point3D(-.3, 0, 0));
            if (Keyboard[Key.Right])
                _localWorld.TranslateLocal(new Point3D(.3, 0, 0));

            if (Keyboard[Key.W])
                _localWorld.RotateAzimuthLocal(new Angle3D { AngleDegrees = 5 });
            if (Keyboard[Key.S])
                _localWorld.RotateAzimuthLocal(new Angle3D { AngleDegrees = -5 });
            if (Keyboard[Key.A])
                _localWorld.RotateAngleLocal(new Angle3D { AngleDegrees = 5 });
            if (Keyboard[Key.D])
                _localWorld.RotateAngleLocal(new Angle3D { AngleDegrees = -5 });
            if (Keyboard[Key.Space])
                _mouseCapture = false;

            if (Keyboard[Key.Escape])
                Exit();

            _zoom = .1 + (Mouse.Wheel * .01);
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            _mouseCapture = true;
            Cursor.Hide();
            Cursor.Position = PointToScreen(new Point(Width / 2, Height / 2));
        }

        private void MouseMove(object sender, MouseMoveEventArgs e)
        {
            if (!_mouseCapture)
            {
                _mouseCapture = true;
                return;
            }

            _localWorld.RotateAngleLocal(new Angle3D { AngleDegrees = -e.XDelta });
            _localWorld.RotateAzimuthLocal(new Angle3D { AngleDegrees = -e.YDelta });

            _mouseCapture = false;
            Cursor.Position = PointToScreen(new Point(Width / 2, Height / 2));
        }



        /// <summary>
        /// Called when it is time to render the next frame. Add your rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            //Title = string.Format("X: {0}, Y: {1}, Z: {2}, Light X: {3}, Y: {4}, Z: {5}", _localWorld.MeX, _localWorld.MeY, _localWorld.MeZ, _localWorld.Lights[0].Position.X, _localWorld.Lights[0].Position.Y, _localWorld.Lights[0].Position.Z);
            Title = string.Format("X: {0}, Y: {1}, Z: {2}", _localWorld.MeX, _localWorld.MeY, _localWorld.MeZ);
            GL.Viewport(0, 0, Width, Height);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();

            GL.Frustum(-_zoom, _zoom, -_zoom * 480 / 640, _zoom * 480 / 640, .1, 100);
            GL.DepthFunc(DepthFunction.Lequal);
            GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();

            GL.Enable(EnableCap.Fog);
            var fogColor = new[] { 0.4f, .4f, 0.4f, 1f };
            GL.Fog(FogParameter.FogColor, fogColor);
            GL.Fog(FogParameter.FogMode, (int)FogMode.Linear);
            GL.Fog(FogParameter.FogStart, 10f);
            GL.Fog(FogParameter.FogEnd, 20f);
            GL.Fog(FogParameter.FogDensity, .8f);

            //GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            //GL.ClearColor(Color4.Gray);
            //GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            //TestShader();
            TestAnimatedLight();

            _localWorld.DrawWorld();
            GL.Disable(EnableCap.Fog);

            GL.Flush();

            SwapBuffers();
        }

        bool moveIn;
        bool objMoveIn;
        private void TestAnimatedLight()
        {
            if (moveIn)
            {
                _localWorld.Lights[0].Position.X -= .1;
                _localWorld.Lights[0].Position.Y -= .1;
                _localWorld.Lights[0].Position.Z -= .1;

                if (_localWorld.Lights[0].Position.Z < -10)
                    moveIn = false;
            }
            else
            {
                _localWorld.Lights[0].Position.X += .1;
                _localWorld.Lights[0].Position.Y += .1;
                _localWorld.Lights[0].Position.Z += .1;

                if (_localWorld.Lights[0].Position.Z > 10)
                    moveIn = true;
            }
            Point3D offset = new Point3D(_localWorld.Meshes[0].Offset.X, _localWorld.Meshes[0].Offset.Y, _localWorld.Meshes[0].Offset.Z);
            

            if (objMoveIn)
            {
                _localWorld.Meshes[0].Offset.X -= .1;

                if (_localWorld.Meshes[0].Offset.X < -12)
                    objMoveIn = false;
            }
            else
            {
                _localWorld.Meshes[0].Offset.X += .1;

                if (_localWorld.Meshes[0].Offset.X > 12)
                    objMoveIn = true;
            }
            offset.X -= _localWorld.Meshes[0].Offset.X;
            offset.Y -= _localWorld.Meshes[0].Offset.Y;
            offset.Z -= _localWorld.Meshes[0].Offset.Z;
            SoundUtils.setCenterLocation(_localWorld.Meshes[0].Offset, offset);
            _localWorld.Meshes[0].Angle = (180 / Math.PI) * (2 * Math.PI) / (10000 / (double)((DateTime.Now.Second * 1000) + DateTime.Now.Millisecond));
        }
        private void TestShader()
        {
            GL.Enable(EnableCap.Normalize);
            GL.Enable(EnableCap.Lighting);
            GL.Enable(EnableCap.ColorMaterial);
            GL.Enable(EnableCap.Light0);
            var diffuseColor = new[] { 1.0f, 1.0f, 1.0f, 1f };
            var ambientColor = new[] { 0.1f, 0.1f, 0.1f, 1f };
            var position = new[] { 0f, 3f, -10f, 1f };
            GL.Light(LightName.Light0, LightParameter.Diffuse, diffuseColor);
            GL.Light(LightName.Light0, LightParameter.Ambient, ambientColor);
            GL.Light(LightName.Light0, LightParameter.Position, position);


            GL.Color3(1f, 0f, 0f);
            float dp = (float)Math.PI / 16; // 16 picked arbitrarily; try other numbers too
            GL.Begin(BeginMode.Triangles);
            for (float theta = 0; theta < 2 * Math.PI; theta += dp)
            {
                for (float phi = 0; phi < Math.PI; phi += dp)
                {
                    GL.Normal3((float)(Math.Cos(theta) * Math.Sin(phi)), (float)Math.Cos(phi), (float)(Math.Sin(theta) * Math.Sin(phi)));
                    GL.Vertex3((float)(Math.Cos(theta) * Math.Sin(phi)), (float)Math.Cos(phi), (float)(Math.Sin(theta) * Math.Sin(phi)));
                    GL.Normal3((float)(Math.Cos(theta + dp) * Math.Sin(phi)), (float)Math.Cos(phi), (float)(Math.Sin(theta + dp) * Math.Sin(phi)));
                    GL.Vertex3((float)(Math.Cos(theta + dp) * Math.Sin(phi)), (float)Math.Cos(phi), (float)(Math.Sin(theta + dp) * Math.Sin(phi)));
                    GL.Normal3((float)(Math.Cos(theta + dp) * Math.Sin(phi + dp)), (float)Math.Cos(phi + dp), (float)(Math.Sin(theta + dp) * Math.Sin(phi + dp)));
                    GL.Vertex3((float)(Math.Cos(theta + dp) * Math.Sin(phi + dp)), (float)Math.Cos(phi + dp), (float)(Math.Sin(theta + dp) * Math.Sin(phi + dp)));
                    GL.Normal3((float)(Math.Cos(theta) * Math.Sin(phi)), (float)Math.Cos(phi), (float)(Math.Sin(theta) * Math.Sin(phi)));
                    GL.Vertex3((float)(Math.Cos(theta) * Math.Sin(phi)), (float)Math.Cos(phi), (float)(Math.Sin(theta) * Math.Sin(phi)));
                    GL.Normal3((float)(Math.Cos(theta + dp) * Math.Sin(phi + dp)), (float)Math.Cos(phi + dp), (float)(Math.Sin(theta + dp) * Math.Sin(phi + dp)));
                    GL.Vertex3((float)(Math.Cos(theta + dp) * Math.Sin(phi + dp)), (float)Math.Cos(phi + dp), (float)(Math.Sin(theta + dp) * Math.Sin(phi + dp)));
                    GL.Normal3((float)(Math.Cos(theta) * Math.Sin(phi + dp)), (float)Math.Cos(phi + dp), (float)(Math.Sin(theta) * Math.Sin(phi + dp)));
                    GL.Vertex3((float)(Math.Cos(theta) * Math.Sin(phi + dp)), (float)Math.Cos(phi + dp), (float)(Math.Sin(theta) * Math.Sin(phi + dp)));
                }
            }
            GL.End();
            GL.Disable(EnableCap.Lighting);
        }

        public void SetupWorld()
        {
            TextureUtils.Initializer();
            var ground = new Surface();
            var platform = new Surface();
            bool oneNotTwo = false;
            for (int i = -50; i < 75; i += 5)
            {
                for (int j = -50; j < 75; j += 5)
                {
                    var gWall = new Wall(Color.Green);
                    gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                    oneNotTwo = !oneNotTwo;
                    gWall.Points.Add(new Point3D(i, -1.1, j + 5));
                    gWall.Points.Add(new Point3D(i + 5, -1.1, j + 5));
                    gWall.Points.Add(new Point3D(i + 5, -1.1, j));
                    gWall.Points.Add(new Point3D(i, -1.1, j));
                    ground.Walls.Add(gWall);
                }
            }

            for (int i = 45; i < 75; i += 5)
            {
                for (int j = 50; j < 75; j += 5)
                {

                    var gWall = new Wall(Color.Green);
                    gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                    oneNotTwo = !oneNotTwo;
                    gWall.Points.Add(new Point3D(i, 4, j + 5));
                    gWall.Points.Add(new Point3D(i + 5, 4, j + 5));
                    gWall.Points.Add(new Point3D(i + 5, 4, j));
                    gWall.Points.Add(new Point3D(i, 4, j));
                    ground.Walls.Add(gWall);
                }
            }
            {
                int j = -60;
                for (int i = -20; i < 20; i += 5)
                {

                    var gWall = new Wall(Color.Gray);
                    gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                    oneNotTwo = !oneNotTwo;
                    gWall.Points.Add(new Point3D(i, 4, j));
                    gWall.Points.Add(new Point3D(i + 5, 4, j));
                    gWall.Points.Add(new Point3D(i + 5, -1.1, j));
                    gWall.Points.Add(new Point3D(i, -1.1, j));
                    platform.Walls.Add(gWall);
                }
                j = -20;
                for (int i = -20; i < 20; i += 5)
                {
                    if (i == 0) continue;
                    var gWall = new Wall(Color.Gray);
                    gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                    oneNotTwo = !oneNotTwo;
                    gWall.Points.Add(new Point3D(i, 4, j));
                    gWall.Points.Add(new Point3D(i + 5, 4, j));
                    gWall.Points.Add(new Point3D(i + 5, -1.1, j));
                    gWall.Points.Add(new Point3D(i, -1.1, j));
                    platform.Walls.Add(gWall);
                }
            }
            {
                int i = -20;
                for (int j = -60; j < -20; j += 5)
                {

                    var gWall = new Wall(Color.Gray);
                    gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                    oneNotTwo = !oneNotTwo;
                    gWall.Points.Add(new Point3D(i, 4, j));
                    gWall.Points.Add(new Point3D(i, 4, j+5));
                    gWall.Points.Add(new Point3D(i, -1.1, j+5));
                    gWall.Points.Add(new Point3D(i, -1.1, j));
                    platform.Walls.Add(gWall);
                }
                i = 20;
                for (int j = -60; j < -20; j += 5)
                {

                    var gWall = new Wall(Color.Gray);
                    gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                    oneNotTwo = !oneNotTwo;
                    gWall.Points.Add(new Point3D(i, 4, j));
                    gWall.Points.Add(new Point3D(i, 4, j + 5));
                    gWall.Points.Add(new Point3D(i, -1.1, j + 5));
                    gWall.Points.Add(new Point3D(i, -1.1, j));
                    platform.Walls.Add(gWall);
                }
            }
            for (int h = 0; h < 10; h++)
            {
                double hOffset = (h * 10.2);

                for (int i = -20; i < 20; i += 5)
                {
                    for (int j = -60; j < -20; j += 5)
                    {

                        var gWall = new Wall(Color.Green);
                        gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                        oneNotTwo = !oneNotTwo;
                        gWall.Points.Add(new Point3D(i, 4 + hOffset, j + 5));
                        gWall.Points.Add(new Point3D(i + 5, 4 + hOffset, j + 5));
                        gWall.Points.Add(new Point3D(i + 5, 4 + hOffset, j));
                        gWall.Points.Add(new Point3D(i, 4 + hOffset, j));
                        platform.Walls.Add(gWall);
                    }
                }

                for (int i = -20; i < 20; i += 5)
                {
                    for (int j = 0; j < 40; j += 5)
                    {

                        var gWall = new Wall(Color.Green);
                        gWall.Texture = oneNotTwo ? TextureUtils.GROUND : TextureUtils.GROUND2; ;
                        oneNotTwo = !oneNotTwo;
                        gWall.Points.Add(new Point3D(i, 9.1 + hOffset, j + 5));
                        gWall.Points.Add(new Point3D(i + 5, 9.1 + hOffset, j + 5));
                        gWall.Points.Add(new Point3D(i + 5, 9.1 + hOffset, j));
                        gWall.Points.Add(new Point3D(i, 9.1 + hOffset, j));
                        platform.Walls.Add(gWall);
                    }
                }
                var rmp1 = new Ramp(Color.Red, new Point3D(0, -1.1 + hOffset, 0), new Point3D(0, 4 + hOffset, -20), 5);
                var rmp2 = new Ramp(Color.Red, new Point3D(10, 4 + hOffset, -20), new Point3D(10, 9.1 + hOffset, 0), 5);
                platform.Walls.Add(rmp1);
                platform.Walls.Add(rmp2);

            }

            var ramp = new Ramp(Color.Red, new Point3D(50, -1.1, 15), new Point3D(50, 4, 50), 5);
            var ramp3 = new Ramp(Color.Red, new Point3D(-10, -1.1, -5), new Point3D(-30, 4, -5), 5);
            var ramp4 = new Ramp(Color.Red, new Point3D(-30, 4, -5), new Point3D(-50, -1.1, -5), 5);
            ground.Walls.Add(ramp);
            ground.Walls.Add(ramp3);
            ground.Walls.Add(ramp4);

            var mesh = ObjLoader.LoadStream("cameraDisplay.obj");
            mesh.Offset = new Point3D(0, 0, 0);

            var light = new Light
                            {
                                DiffuseColor = new Color4(1f, 1f, 1f, 1f),
                                Position = new Point3D(0, 5, 0),
                                AmbientColor = new Color4(.02f, .02f, .02f, 1),
                                Particle = true,
                                Positional = true
                            };

            _localWorld.Meshes.Add(mesh);
            _localWorld.Geometry.Add(ground);
            _localWorld.Geometry.Add(platform);
            _localWorld.Lights.Add(light);
            _localWorld.Collision = true;
        }


        static void Main(string[] args)
        {
            using (OpenTK.Audio.AudioContext context = new OpenTK.Audio.AudioContext())
            {
                SoundUtils.Intialize();
                using (var game = new Game())
                {
                    game.Run(30.0);
                }
            }
        }
    }
}
